/*    
      glm.c
      Nate Robins, 1997, 2000
      nate@pobox.com, http://www.pobox.com/~nate
 
      Wavefront OBJ model file format reader/writer/manipulator.

      Includes routines for generating smooth normals with
      preservation of edges, welding redundant vertices & texture
      coordinate generation (spheremap and planar projections) + more.
  
*/
import javax.media.opengl.*;
import javax.media.opengl.glu.*;
import com.jogamp.opengl.util.*;
import java.util.*;
import java.io.*;
import java.nio.*;
/*

#include <math.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <assert.h>
#include "glm.h"
*/



// #define T(x) (model.triangles[(x)])
// = Triangle(i)  


// from glm.h

class GLMnode {
    int index;
    boolean averaged;
    GLMnode next;
} 

class GLMtransform {
   static final int ROT = 0;
   static final int TRA = 1;
   static final int SCA = 2;		
   float angle;
	float x;
	float y;
	float z;
	int type;
	GLMtransform(int type, float x, float y, float z, float angle) {
	    this.x = x;
		 this.y = y;
		 this.z = z;
		 this.type = type;
		 this.angle = angle;
	}
}

/* GLMmaterial: Structure that defines a material in a model. 
 */
 class GLMmaterial 
{
  String  name;                 /* name of material */
  float[] diffuse = new float[4];           /* diffuse component */
  float[] ambient = new float[4];           /* ambient component */
  float[] specular = new float[4];          /* specular component */
  float[] emmissive = new float[4];         /* emmissive component */
  float shininess;            /* specular exponent */
} 

/* GLMtriangle: Structure that defines a triangle in a model.
 */
 class GLMtriangle {
  int[] vindices = new int[3];           /* array of triangle vertex indices */
  int[] nindices = new int[3];           /* array of triangle normal indices */
  int[] tindices = new int[3];           /* array of triangle texcoord indices*/
  int findex;                /* index of triangle facet normal */
} 

/* GLMgroup: Structure that defines a group in a model.
 */
 
 class GLMgroup {
  String         name;           /* name of this group */
  int            displayListNum; /* supports creation of display lists */
  static int     displayListCounter = 0;
  int            numtriangles;   /* number of triangles in this group */
  int[]          triangles;      /* array of triangle indices */
  int            material;       /* index to material for group */
  GLMgroup          next;           /* pointer to next group in model */
  ArrayList<GLMtransform> transforms = new ArrayList<GLMtransform>();
  void addTransform(GLMtransform tr) {
     transforms.add(tr);
  }
}

/* GLMmodel: Structure that defines a model.
 */
class GLMmodel {
  String    pathname;            /* path to this model */
  String    mtllibname;          /* name of the material library */

  int   numvertices;         /* number of vertices in model */
  float[] vertices;            /* array of vertices  */

  int   numnormals;          /* number of normals in model */
  float[] normals;             /* array of normals */

  int   numtexcoords;        /* number of texcoords in model */
  float[] texcoords;           /* array of texture coordinates */

  int   numfacetnorms;       /* number of facetnorms in model */
  float[] facetnorms;          /* array of facetnorms */

  int       numtriangles;    /* number of triangles in model */
  GLMtriangle[] triangles;       /* array of triangles */

  int       nummaterials;    /* number of materials in model */
  GLMmaterial[] materials;       /* array of materials */

  int       numgroups;       /* number of groups in model */
  GLMgroup    groups;          /* linked list of groups */

  float[] position = new float[3];          /* position of the model */

  boolean wireFrame = false;

  void addTransform(String groupName, GLMtransform trans) {
      GLMgroup curr = groups;
		while (curr != null) {
		    if (curr.name.equals(groupName)) {
			     curr.addTransform(trans);
			     break;
			 } 
		    curr = curr.next;
		}
  }
  
  void clearTransforms() {
      GLMgroup curr = groups;
		while (curr != null) {
		    curr.transforms.clear();   
			 curr = curr.next;
		}
  }    
}



public class GLM {


    final static int GLM_NONE   =  0;            /* render with only vertices */
    final static int GLM_FLAT   =  (1 << 0);       /* render with facet normals */
    final static int GLM_SMOOTH  = (1 << 1);       /* render with vertex normals */
    final static int GLM_TEXTURE = (1 << 2);       /* render with texture coords */
    final static int GLM_COLOR   = (1 << 3);       /* render with colors */
    final static int GLM_MATERIAL = (1 << 4);       /* render with materials */




/* _GLMnode: general purpose node */
//typedef struct _GLMnode 




/* glmMax: returns the maximum of two floats */
static float
glmMax(float a, float b) 
{
    if (b > a)
        return b;
    return a;
}

/* glmAbs: returns the absolute value of a float */
static float
glmAbs(float f)
{
    if (f < 0)
        return -f;
    return f;
}

/* glmDot: compute the dot product of two vectors
 *
 * u - array of 3 floats (float u[3])
 * v - array of 3 floats (float v[3])
 */
static float glmDot(float u[], int iu, float v[], int iv)
{
  //  assert(u); assert(v);
    
    return u[iu+0]*v[iv+0] + u[iu+1]*v[iv+1] + u[iu+2]*v[iv+2];
}

/* glmCross: compute the cross product of two vectors
 *
 * u - array of 3 floats (float u[3])
 * v - array of 3 floats (float v[3])
 * n - array of 3 floats (float n[3]) to return the cross product in
 */
static void
glmCross(float u[], float v[], float n[], int ni)
{
  //  assert(u); assert(v); assert(n);
    
    n[ni+0] = u[1]*v[2] - u[2]*v[1];
    n[ni+1] = u[2]*v[0] - u[0]*v[2];
    n[ni+2] = u[0]*v[1] - u[1]*v[0];
}

/* glmNormalize: normalize a vector
 *
 * v - array of 3 floats (float v[3]) to be normalized
 */
static void
glmNormalize(float v[],int stI)  // startIndex
{
    float l;
    
  //  assert(v);
    
    l = (float)Math.sqrt(v[stI+0]*v[stI+0] + v[stI+1]*v[stI+1] + v[stI+2]*v[stI+2]);
    v[stI+0] /= l;
    v[stI+1] /= l;
    v[stI+2] /= l;
}

/* glmEqual: compares two vectors and returns GL_TRUE if they are
 * equal (within a certain threshold) or GL_FALSE if not. An epsilon
 * that works fairly well is 0.000001.
 *
 * u - array of 3 floats (float u[3])
 * v - array of 3 floats (float v[3]) 
 */
static boolean
glmEqual(float u[], float v[], float epsilon)
{
    if (glmAbs(u[0] - v[0]) < epsilon &&
        glmAbs(u[1] - v[1]) < epsilon &&
        glmAbs(u[2] - v[2]) < epsilon) 
    {
        return true;
    }
    return false;
}

/* glmWeldVectors: eliminate (weld) vectors that are within an
 * epsilon of each other.
 *
 * vectors     - array of float[3]'s to be welded
 * numvectors - number of float[3]'s in vectors
 * epsilon     - maximum difference between vectors 
 *
 */
 
 /*     glmWeldVectors is called by glmWeld,
        but the latters seems not to be used by
		  projection.c, the application that display Al, the gangster.
		  */
		  
		  /*   NOT IN USE ??
		  
static float[] glmWeldVectors(float[] vectors, int* numvectors, float epsilon)
{
    float* copies;
    int copied;
    int i, j;
    
    copies = (float*)malloc(sizeof(float) * 3 * (*numvectors + 1));
    memcpy(copies, vectors, (sizeof(float) * 3 * (*numvectors + 1)));
    
    copied = 1;
    for (i = 1; i <= *numvectors; i++) {
        for (j = 1; j <= copied; j++) {
            if (glmEqual(&vectors[3 * i], &copies[3 * j], epsilon)) {
                goto duplicate;
            }
        }
        
        // must not be any duplicates -- add to the copies array 
        copies[3 * copied + 0] = vectors[3 * i + 0];
        copies[3 * copied + 1] = vectors[3 * i + 1];
        copies[3 * copied + 2] = vectors[3 * i + 2];
        j = copied;             //* pass this along for below 
        copied++;
        
duplicate:
        //set the first component of this vector to point at the correct
        //index into the new copies array 
        vectors[3 * i + 0] = (float)j;
    }
    
    *numvectors = copied-1;
    return copies;
}

*/

/* glmFindGroup: Find a group in the model */
static GLMgroup
glmFindGroup(GLMmodel model, String name)
{
    GLMgroup group;
    
  //  assert(model);
	 
    group = model.groups;
    while(group != null) {
        if (name.equals(group.name))  //    !strcmp(name, group.name))
            break;
        group = group.next;
    }
    
    return group;
}

/* glmAddGroup: Add a group to the model */
static GLMgroup
glmAddGroup(GLMmodel model, String name)
{
    GLMgroup group;
    
    group = glmFindGroup(model, name);  // does it already exist?
	 
    if (group == null) {
        group = new GLMgroup();
        group.name = name;
		  group.displayListNum = -1;
        group.material = 0;
        group.numtriangles = 0;
        group.triangles = null;
        group.next = model.groups;
        model.groups = group;
        model.numgroups++;
    }
    
    return group;
}

/* glmFindGroup: Find a material in the model */
static int
glmFindMaterial(GLMmodel model, String name)
{
    int i;
    
    /* XXX doing a linear search on a string key'd list is pretty lame,
    but it works and is fast enough for now. */
    for (i = 0; i < model.nummaterials; i++) {
        if (model.materials[i].name.equalsIgnoreCase(name))
           return i;
    }
    
    /* didn't find the name, so print a warning and return the default
    material (0). */
    System.out.println("glmFindMaterial():  can't find material \""+name+"\".\n");
    return 0;
}


/* glmDirName: return the directory given a path
 *
 * path - filesystem path
 *
 * NOTE: the return value should be free'd.
 */
static String
glmDirName(String path)
{
    String dir;
    int s;
    
    dir = path;
    
	 s = dir.lastIndexOf('/');
    //s = strrchr(dir, '/');  // find last occurrence of /
    if (s != -1)
        dir = dir.substring(0,s+1);
    else
        dir = "";
    
    return dir;
}


/* glmReadMTL: read a wavefront material library file
 *
 * model - properly initialized GLMmodel structure
 * name  - name of the material library
 */
static void
glmReadMTL(GLMmodel model, String name)
{
    Scanner scanner = null;
   
    String dir;
    String filename;
    String buf;
    int nummaterials, i;
    
    dir = glmDirName(model.pathname);
    filename = dir+name;  	 
	 
    try {
	    scanner = new Scanner(new File(filename));
	 } catch(Exception e) {
	    System.out.print(
		     "glmReadMTL() failed: can't open material file \""+
			  filename+"\".\n");
		 System.exit(1);
	 }
	 
    
    /* count the number of materials in the file */
    nummaterials = 1;     // there is a default material! jm
    while(scanner.hasNextLine()) {
	     buf = scanner.nextLine();
		  
        if (buf.startsWith("newmtl")) {
            nummaterials++;          
	     }
    }
	 
    try {
	    scanner = new Scanner(new File(filename));
	 } catch(Exception e) {
	    System.out.print(
		     "glmReadMTL() failed: can't open material file \""+
			  filename+"\".\n");
		 System.exit(1);
	 }
    
    model.materials = new GLMmaterial[nummaterials];
    model.nummaterials = nummaterials;
    
    /* set the default material */
    for (i = 0; i < nummaterials; i++) {
	     model.materials[i] = new GLMmaterial();
        model.materials[i].name = null;
        model.materials[i].shininess = 65.0f;
        model.materials[i].diffuse[0] = 0.8f;
        model.materials[i].diffuse[1] = 0.8f;
        model.materials[i].diffuse[2] = 0.8f;
        model.materials[i].diffuse[3] = 1.0f;
        model.materials[i].ambient[0] = 0.2f;
        model.materials[i].ambient[1] = 0.2f;
        model.materials[i].ambient[2] = 0.2f;
        model.materials[i].ambient[3] = 1.0f;
        model.materials[i].specular[0] = 0.0f;
        model.materials[i].specular[1] = 0.0f;
        model.materials[i].specular[2] = 0.0f;
        model.materials[i].specular[3] = 1.0f;
    }
    model.materials[0].name = "default";
    
    /* now, read in the data */
    nummaterials = 0;
	 
    while(scanner.hasNextLine()) {
	     buf = scanner.nextLine();	 
	     if (buf.equals("")) continue;
        
        switch(buf.charAt(0)) {
        case '#':               /* comment */
            /* skip rest of line */
            break;
        case 'n':               /* newmtl */
		      Scanner lineSc = new Scanner(buf);
				lineSc.next();  // skip newmtl
            nummaterials++;
            model.materials[nummaterials].name = lineSc.next();
            break;
        case 'N':
		      lineSc = new Scanner(buf);
				lineSc.next();  // skip N
						  
				model.materials[nummaterials].shininess = (float)lineSc.nextDouble();
            /* wavefront shininess is from [0, 1000], so scale for OpenGL */
				// jm opengl  and jogl??
            model.materials[nummaterials].shininess /= 1000.0;
            model.materials[nummaterials].shininess *= 128.0;
            break;
        case 'K':
		  		lineSc = new Scanner(buf);
				lineSc.next();  // get rid of K
            switch(buf.charAt(1)) {
            case 'd':
                model.materials[nummaterials].diffuse[0] = (float)lineSc.nextDouble();
                model.materials[nummaterials].diffuse[1] = (float)lineSc.nextDouble();
                model.materials[nummaterials].diffuse[2] = (float)lineSc.nextDouble();
                break;
            case 's':
                model.materials[nummaterials].specular[0] = (float)lineSc.nextDouble();
                model.materials[nummaterials].specular[1] = (float)lineSc.nextDouble();
                model.materials[nummaterials].specular[2]  = (float)lineSc.nextDouble();
                break;
            case 'a':
                model.materials[nummaterials].ambient[0] = (float)lineSc.nextDouble();
                model.materials[nummaterials].ambient[1]  =(float) lineSc.nextDouble();
                model.materials[nummaterials].ambient[2]  =(float) lineSc.nextDouble();
                break;
            default:
                /* skip rest of line */ 
                break;
            }
            break;
            default:
                /* skip rest of line */
                break;
        }
    }
}

/* glmWriteMTL: write a wavefront material library file
 *
 * model   - properly initialized GLMmodel structure
 * modelpath  - pathname of the model being written
 * mtllibname - name of the material library to be written
 */
 
/*   LOWER PRIORITY for now - jm

static void
glmWriteMTL(GLMmodel* model, String modelpath, String mtllibname)
{
    FILE* file;
    String dir;
    String filename;
    GLMmaterial* material;
    int i;
    
    dir = glmDirName(modelpath);
    filename = (String)malloc(sizeof(char) * (strlen(dir)+strlen(mtllibname)));
    strcpy(filename, dir);
    strcat(filename, mtllibname);
    free(dir);
    
    //* open the file 
    file = fopen(filename, "w");
    if (!file) {
        fSystem.out.print(stderr, "glmWriteMTL() failed: can't open file \"%s\".\n",
            filename);
        exit(1);
    }
    free(filename);
    
    //* spit out a header 
    fSystem.out.print(file, "#  \n");
    fSystem.out.print(file, "#  Wavefront MTL generated by GLM library\n");
    fSystem.out.print(file, "#  \n");
    fSystem.out.print(file, "#  GLM library\n");
    fSystem.out.print(file, "#  Nate Robins\n");
    fSystem.out.print(file, "#  ndr@pobox.com\n");
    fSystem.out.print(file, "#  http://www.pobox.com/~ndr\n");
    fSystem.out.print(file, "#  \n\n");
    
    for (i = 0; i < model.nummaterials; i++) {
        material = &model.materials[i];
        fSystem.out.print(file, "newmtl %s\n", material.name);
        fSystem.out.print(file, "Ka %f %f %f\n", 
            material.ambient[0], material.ambient[1], material.ambient[2]);
        fSystem.out.print(file, "Kd %f %f %f\n", 
            material.diffuse[0], material.diffuse[1], material.diffuse[2]);
        fSystem.out.print(file, "Ks %f %f %f\n", 
            material.specular[0],material.specular[1],material.specular[2]);
        fSystem.out.print(file, "Ns %f\n", material.shininess / 128.0 * 1000.0);
        fSystem.out.print(file, "\n");
    }
}

*/

/* glmFirstPass: first pass at a Wavefront OBJ file that gets all the
 * statistics of the model (such as #vertices, #normals, etc)
 *
 * model - properly initialized GLMmodel structure
 * file  - (fopen'd) file descriptor 
 */
static void
glmFirstPass(GLMmodel model, String fileName) 
{
    int numvertices;        /* number of vertices in model */
    int numnormals;         /* number of normals in model */
    int numtexcoords;       /* number of texcoords in model */
    int numtriangles;       /* number of triangles in model */
    GLMgroup group;           /* current group */
    int v, n, t;
    String buf;
	 
	 
    Scanner scanner = null;
    try {
	    scanner = new Scanner(new File(fileName));
	 } catch(Exception e) {
	    System.out.print(
		     "glmReadMTL() failed: can't open material file \""+
			  fileName+"\".\n");
		 System.exit(1);
	 }	 
	 
    /* make a default group */
    group = glmAddGroup(model, "default");
    
    numvertices = numnormals = numtexcoords = numtriangles = 0;
	 
    while(scanner.hasNextLine()) {
	     buf = scanner.nextLine();
		  if (buf.trim().equals(""))
		      continue;
        switch(buf.charAt(0)) {
        case '#':               /* comment */
            /* eat up rest of line */ 
            break;
        case 'v':               /* v, vn, vt */
            switch(buf.charAt(1)) {
            case ' ':          /* vertex */
                /* eat up rest of line */ 
                numvertices++;
                break;
            case 'n':           /* normal */
                /* eat up rest of line */ 
                numnormals++;
                break;
            case 't':           /* texcoord */
                /* eat up rest of line */ 
                numtexcoords++;
                break;
            default:
                System.out.println("glmFirstPass(): Unknown token \""+buf+"\".");
                System.exit(1);
                break;
            }
            break;
            case 'm':  // mtllib al.mtl
				    Scanner lineSc = new Scanner(buf);
					 lineSc.next(); // skip mtllib
                model.mtllibname = lineSc.next();
                glmReadMTL(model, model.mtllibname );
                break;
            case 'u':  // what would this be?
                /* eat up rest of line */
                break;
            case 'g':               /* group */    
				    // SINGLE_STRING_GROUP_NAMES:   g hat  vs.   g top hat
                buf = buf.substring(1).trim();
                group = glmAddGroup(model, buf);
                break;
            case 'f':               /* face */
				    buf = buf.substring(2).trim();
					 String vert;
					 Scanner sc;
                v = n = t = 0;
              					 
                if (buf.contains("//")) {   /* v//n */
					     Scanner scan = new Scanner(buf);
						  scan.useDelimiter("\\s*//\\s*|\\s+");  // whitespace or //   
						  v = scan.nextInt();
                    n = scan.nextInt();	
						  
						  v = scan.nextInt();
                    n = scan.nextInt();
						  
						  v = scan.nextInt();
                    n = scan.nextInt();						  						
						  					  
                    numtriangles++;
                    group.numtriangles++;
                    while(scan.hasNext()) {   
                        v = scan.nextInt();
                        n = scan.nextInt();						      
                        numtriangles++;
                        group.numtriangles++;
                    }
                } else if ( (vert = (sc = new Scanner(buf)).next()).matches("\\d*/\\d*/\\d*")) {
                    /* v/t/n */
						  Scanner vScanner = new Scanner(vert).useDelimiter("\\s*/\\s*");
                    v = vScanner.nextInt();
						  t = vScanner.nextInt();
						  n = vScanner.nextInt();

						  vScanner = new Scanner(sc.next()).useDelimiter("\\s*/\\s*");
                    v = vScanner.nextInt();
						  t = vScanner.nextInt();
						  n = vScanner.nextInt();
						  
						  vScanner = new Scanner(sc.next()).useDelimiter("\\s*/\\s*");
                    v = vScanner.nextInt();
						  t = vScanner.nextInt();
						  n = vScanner.nextInt();						  						  
						  
						  
                    numtriangles++;
                    group.numtriangles++;
                    while(sc.hasNext()) {
						      vScanner = new Scanner(sc.next()).useDelimiter("\\s*/\\s*");
                        v = vScanner.nextInt();
						      t = vScanner.nextInt();
						      n = vScanner.nextInt();						  
                        numtriangles++;
                        group.numtriangles++;
                    }
                } else if ( (vert = (sc = new Scanner(buf)).next()).matches(  "\\d*/\\d*")) {
                    /* v/t */
						  Scanner vScanner = new Scanner(buf).useDelimiter("\\s*/\\s*|\\s+");  // whitespace or // 
                    v = vScanner.nextInt();
						  t = vScanner.nextInt();
						  
                    v = vScanner.nextInt();
						  t = vScanner.nextInt();
						  
                    v = vScanner.nextInt();
						  t = vScanner.nextInt();						  						  
                    numtriangles++;
                    group.numtriangles++;
                    while(vScanner.hasNext()) { 
                        v = vScanner.nextInt();
						      t = vScanner.nextInt();						  
                        numtriangles++;
                        group.numtriangles++;
                    }
                } else {
					     Scanner vScanner = new Scanner(buf).useDelimiter("\\s*//\\s*|\\s+"); 
                    /* v */
                    v = vScanner.nextInt();
                    v = vScanner.nextInt();
                    v = vScanner.nextInt();						  						  
                    numtriangles++;
                    group.numtriangles++;
                    while(vScanner.hasNext()) {
						  	   v = vScanner.nextInt();
                        numtriangles++;
                        group.numtriangles++;
                    }
                }
                break;
                
            default:
                /* eat up rest of line */
                break;
        }
  }
  
  /* set the stats in the model structure */
  model.numvertices  = numvertices;
  model.numnormals   = numnormals;
  model.numtexcoords = numtexcoords;
  model.numtriangles = numtriangles;
  
  /* allocate memory for the triangles in each group */
  group = model.groups;
  while(group != null) {
      group.triangles =  new int[group.numtriangles]; //   (int*)malloc(sizeof(int) * group.numtriangles);
      group.numtriangles = 0;
      group = group.next;
  }
}

/* glmSecondPass: second pass at a Wavefront OBJ file that gets all
 * the data.
 *
 * model - properly initialized GLMmodel structure
 * file  - (fopen'd) file descriptor 
 */

static void
glmSecondPass(GLMmodel model, String fileName) {
    int numvertices;        /* number of vertices in model */
    int numnormals;         /* number of normals in model */
    int numtexcoords;       /* number of texcoords in model */
    int numtriangles;       /* number of triangles in model */
    float vertices[];         /* array of vertices  */
    float normals[];          /* array of normals */
    float texcoords[];        /* array of texture coordinates */
    GLMgroup group;           /* current group pointer */
    int material;           /* current material */
    int v, n, t;
    String buf;
    
    /* set the pointer shortcuts */
    vertices       = model.vertices;
    normals    = model.normals;
    texcoords    = model.texcoords;
    group      = model.groups;
    
    /* on the second pass through the file, read all the data into the
    allocated arrays */
    numvertices = numnormals = numtexcoords = 1;
    numtriangles = 0;
    material = 0;
	 
    Scanner scanner = null;
    try {
	    scanner = new Scanner(new File(fileName));
	 } catch(Exception e) {
	    System.out.print(
		     "glmReadMTL() failed: can't open material file \""+
			  fileName+"\".\n");
		 System.exit(1);
	 }	 
	 
	 
    while(scanner.hasNextLine()) {
	     buf = scanner.nextLine();
		  if (buf.trim().equals(""))
		      continue;	 
	 
   	 
	 
        switch(buf.charAt(0)) {
        case '#':               /* comment */
            /* eat up rest of line */
            break;
        case 'v':               /* v, vn, vt */
				Scanner vScan = new Scanner(buf.substring(2));
				switch(buf.charAt(1)) {
            case ' ':          /* vertex */
                vertices[3 * numvertices + 0] = vScan.nextFloat(); 
                vertices[3 * numvertices + 1] = vScan.nextFloat(); 
                vertices[3 * numvertices + 2] = vScan.nextFloat();
                numvertices++;
                break;
            case 'n':           /* normal */
                normals[3 * numnormals + 0] = vScan.nextFloat();
                normals[3 * numnormals + 1] = vScan.nextFloat();
                normals[3 * numnormals + 2] = vScan.nextFloat();
                numnormals++;
                break;
            case 't':           /* texcoord */
                texcoords[2 * numtexcoords + 0] = vScan.nextFloat();
                texcoords[2 * numtexcoords + 1] = vScan.nextFloat();
                numtexcoords++;
                break;
            }
            break;
            case 'u':  // line =  "usemtl somemtl"
				    //System.out.println(buf);
				    /*
                fgets(buf, sizeof(buf), file);
                sscanf(buf, "%s %s", buf, buf);
					 */
                group.material = material = glmFindMaterial(model, buf.substring(6).trim() );
	
                break;
            case 'g':               /* group */
                /* eat up rest of line */
                buf = buf.substring(1).trim();
                group = glmFindGroup(model, buf);
                group.material = material;
                break;
            case 'f':               /* face */
					 buf = buf.substring(2).trim();
					 String vert;
					 Scanner sc;
                v = n = t = 0; 
                /* can be one of "+ +", "+ +"//"+ +", "+ +"/"+ +", "+ +"/"+ +"/"+ +" "+ +"//"+ +" */
                if (buf.contains("//")) { /* v//n */
					     Scanner scan = new Scanner(buf);
						  scan.useDelimiter("\\s*//\\s*|\\s+");
						  model.triangles[numtriangles] = new GLMtriangle();
						  v = scan.nextInt();
                    n = scan.nextInt();						  
                    model.triangles[numtriangles].vindices[0] = v < 0 ? v + numvertices : v;
                    model.triangles[numtriangles].nindices[0] = n < 0 ? n + numnormals : n;
						  v = scan.nextInt();
                    n = scan.nextInt();	 
                    model.triangles[numtriangles].vindices[1] = v < 0 ? v + numvertices : v;
                    model.triangles[numtriangles].nindices[1] = n < 0 ? n + numnormals : n;
						  v = scan.nextInt();
                    n = scan.nextInt();	 
                    model.triangles[numtriangles].vindices[2] = v < 0 ? v + numvertices : v;
                    model.triangles[numtriangles].nindices[2] = n < 0 ? n + numnormals : n;
						  
                    group.triangles[group.numtriangles++] = numtriangles;
                    numtriangles++;
                    while(scan.hasNext()) {
                        v = scan.nextInt();
                        n = scan.nextInt();		
						      model.triangles[numtriangles] = new GLMtriangle();												  
                        model.triangles[numtriangles].vindices[0] = model.triangles[numtriangles-1].vindices[0];
                        model.triangles[numtriangles].nindices[0] = model.triangles[numtriangles-1].nindices[0];
                        model.triangles[numtriangles].vindices[1] = model.triangles[numtriangles-1].vindices[2];
                        model.triangles[numtriangles].nindices[1] = model.triangles[numtriangles-1].nindices[2];
                        model.triangles[numtriangles].vindices[2] = v < 0 ? v + numvertices : v;
                        model.triangles[numtriangles].nindices[2] = n < 0 ? n + numnormals : n;
                        group.triangles[group.numtriangles++] = numtriangles;
                        numtriangles++;
                    }
                } else if ((vert = (sc = new Scanner(buf)).next()).matches("\\d*/\\d*/\\d*")) {
					      /* v/t/n */
						  Scanner vScanner = new Scanner(vert).useDelimiter("\\s*/\\s*");
                    v = vScanner.nextInt();
						  t = vScanner.nextInt();
						  n = vScanner.nextInt();
						  model.triangles[numtriangles] = new GLMtriangle();						  
						  							
                    model.triangles[numtriangles].vindices[0] = v < 0 ? v + numvertices : v;
                    model.triangles[numtriangles].tindices[0] = t < 0 ? t + numtexcoords : t;
                    model.triangles[numtriangles].nindices[0] = n < 0 ? n + numnormals : n;
						  
						  vScanner = new Scanner(sc.next()).useDelimiter("\\s*/\\s*");
                    v = vScanner.nextInt();
						  t = vScanner.nextInt();
						  n = vScanner.nextInt();
                    model.triangles[numtriangles].vindices[1] = v < 0 ? v + numvertices : v;
                    model.triangles[numtriangles].tindices[1] = t < 0 ? t + numtexcoords : t;
                    model.triangles[numtriangles].nindices[1] = n < 0 ? n + numnormals : n;

						  						  
						  vScanner = new Scanner(sc.next()).useDelimiter("\\s*/\\s*");
                    v = vScanner.nextInt();
						  t = vScanner.nextInt();
						  n = vScanner.nextInt();
                    model.triangles[numtriangles].vindices[2] = v < 0 ? v + numvertices : v;
                    model.triangles[numtriangles].tindices[2] = t < 0 ? t + numtexcoords : t;
                    model.triangles[numtriangles].nindices[2] = n < 0 ? n + numnormals : n;
                    group.triangles[group.numtriangles++] = numtriangles;
                    numtriangles++;
                    while(vScanner.hasNext()) {
                        v = vScanner.nextInt();
						      t = vScanner.nextInt();
						      n = vScanner.nextInt();	
						      model.triangles[numtriangles] = new GLMtriangle();														  
                        model.triangles[numtriangles].vindices[0] = model.triangles[numtriangles-1].vindices[0];
                        model.triangles[numtriangles].tindices[0] = model.triangles[numtriangles-1].tindices[0];
                        model.triangles[numtriangles].nindices[0] = model.triangles[numtriangles-1].nindices[0];
                        model.triangles[numtriangles].vindices[1] = model.triangles[numtriangles-1].vindices[2];
                        model.triangles[numtriangles].tindices[1] = model.triangles[numtriangles-1].tindices[2];
                        model.triangles[numtriangles].nindices[1] = model.triangles[numtriangles-1].nindices[2];
                        model.triangles[numtriangles].vindices[2] = v < 0 ? v + numvertices : v;
                        model.triangles[numtriangles].tindices[2] = t < 0 ? t + numtexcoords : t;
                        model.triangles[numtriangles].nindices[2] = n < 0 ? n + numnormals : n;
                        group.triangles[group.numtriangles++] = numtriangles;
                        numtriangles++;
                    }
                } else if ((vert = (sc = new Scanner(buf)).next()).matches(  "\\d*/\\d*")) {
                    /* v/t */
						  Scanner vScanner = new Scanner(buf).useDelimiter("\\s*/\\s*|\\s+");  // whitespace or // 
                    v = vScanner.nextInt();
						  t = vScanner.nextInt();				
						  model.triangles[numtriangles] = new GLMtriangle();						  		  
                    model.triangles[numtriangles].vindices[0] = v < 0 ? v + numvertices : v;
                    model.triangles[numtriangles].tindices[0] = t < 0 ? t + numtexcoords : t;
                    v = vScanner.nextInt();
						  t = vScanner.nextInt();               
                    model.triangles[numtriangles].vindices[1] = v < 0 ? v + numvertices : v;
                    model.triangles[numtriangles].tindices[1] = t < 0 ? t + numtexcoords : t;
                    v = vScanner.nextInt();
						  t = vScanner.nextInt();        
                    model.triangles[numtriangles].vindices[2] = v < 0 ? v + numvertices : v;
                    model.triangles[numtriangles].tindices[2] = t < 0 ? t + numtexcoords : t;
                    group.triangles[group.numtriangles++] = numtriangles;
                    numtriangles++;
                    while(vScanner.hasNext()) {
						      v = vScanner.nextInt();
						      t = vScanner.nextInt();
						      model.triangles[numtriangles] = new GLMtriangle();								
                        model.triangles[numtriangles].vindices[0] = model.triangles[numtriangles-1].vindices[0];
                        model.triangles[numtriangles].tindices[0] = model.triangles[numtriangles-1].tindices[0];
                        model.triangles[numtriangles].vindices[1] = model.triangles[numtriangles-1].vindices[2];
                        model.triangles[numtriangles].tindices[1] = model.triangles[numtriangles-1].tindices[2];
                        model.triangles[numtriangles].vindices[2] = v < 0 ? v + numvertices : v;
                        model.triangles[numtriangles].tindices[2] = t < 0 ? t + numtexcoords : t;
                        group.triangles[group.numtriangles++] = numtriangles;
                        numtriangles++;
                    }
                } else {
                    /* v */
                    Scanner vScanner = new Scanner(buf).useDelimiter("\\s*//\\s*|\\s+"); 
						  model.triangles[numtriangles] = new GLMtriangle();
                    v = vScanner.nextInt();						  
                    model.triangles[numtriangles].vindices[0] = v < 0 ? v + numvertices : v;
                    v = vScanner.nextInt();
                    model.triangles[numtriangles].vindices[1] = v < 0 ? v + numvertices : v;
                    v = vScanner.nextInt();
                    model.triangles[numtriangles].vindices[2] = v < 0 ? v + numvertices : v;
                    group.triangles[group.numtriangles++] = numtriangles;
                    numtriangles++;
                    while(vScanner.hasNext()) {
						      v = vScanner.nextInt();
						      model.triangles[numtriangles] = new GLMtriangle();								
                        model.triangles[numtriangles].vindices[0] = model.triangles[numtriangles-1].vindices[0];
                        model.triangles[numtriangles].vindices[1] = model.triangles[numtriangles-1].vindices[2];
                        model.triangles[numtriangles].vindices[2] = v < 0 ? v + numvertices : v;
                        group.triangles[group.numtriangles++] = numtriangles;
                        numtriangles++;
                    }
                }
                break;
                
            default:
                /* eat up rest of line */
                break;
    }
  }
  
  /*
#if 0
  // announce the memory requirements 
  System.out.print(" Memory: "+ +" bytes\n",
      numvertices  * 3*sizeof(float) +
      numnormals   * 3*sizeof(float) * (numnormals ? 1 : 0) +
      numtexcoords * 3*sizeof(float) * (numtexcoords ? 1 : 0) +
      numtriangles * sizeof(GLMtriangle));
#endif
   */
}  
  
  
static GLMmodel
glmReadOBJ(String filename)
{
    GLMmodel model;
  
    

    
    /* allocate a new model */
    model = new GLMmodel();
    model.pathname    = filename;
    model.mtllibname    = null;
    model.numvertices   = 0;
    model.vertices    = null;
    model.numnormals    = 0;
    model.normals     = null;
    model.numtexcoords  = 0;
    model.texcoords       = null;
    model.numfacetnorms = 0;
    model.facetnorms    = null;
    model.numtriangles  = 0;
    model.triangles       = null;
    model.nummaterials  = 0;
    model.materials       = null;
    model.numgroups       = 0;
    model.groups      = null;
    model.position[0]   = 0.0f;
    model.position[1]   = 0.0f;
    model.position[2]   = 0.0f;
    
    /* make a first pass through the file to get a count of the number
    of vertices, normals, texcoords & triangles */
    glmFirstPass(model, filename);
    
    /* allocate memory */
    model.vertices =  new float[ 3 * (model.numvertices + 1) ];
    model.triangles = new GLMtriangle[model.numtriangles];
    if (model.numnormals != 0) {
        model.normals =  new float[ 3 * (model.numnormals + 1)];
    }
    if (model.numtexcoords != 0) {
        model.texcoords = new float[ 2 * (model.numtexcoords + 1)];
    }
  
    
    glmSecondPass(model, filename);
    
 
    return model;
}
  
  





/* glmUnitize: "unitize" a model by translating it to the origin and
 * scaling it to fit in a unit cube around the origin.   Returns the
 * scalefactor used.
 *
 * model - properly initialized GLMmodel structure 
 */
static float
glmUnitize(GLMmodel model)
{
    int i;
    float maxx, minx, maxy, miny, maxz, minz;
    float cx, cy, cz, w, h, d;
    float scale;
    
    //assert(model);
    //assert(model.vertices);
    
    /* get the max/mins */
    maxx = minx = model.vertices[3 + 0];
    maxy = miny = model.vertices[3 + 1];
    maxz = minz = model.vertices[3 + 2];
    for (i = 1; i <= model.numvertices; i++) {
        if (maxx < model.vertices[3 * i + 0])
            maxx = model.vertices[3 * i + 0];
        if (minx > model.vertices[3 * i + 0])
            minx = model.vertices[3 * i + 0];
        
        if (maxy < model.vertices[3 * i + 1])
            maxy = model.vertices[3 * i + 1];
        if (miny > model.vertices[3 * i + 1])
            miny = model.vertices[3 * i + 1];
        
        if (maxz < model.vertices[3 * i + 2])
            maxz = model.vertices[3 * i + 2];
        if (minz > model.vertices[3 * i + 2])
            minz = model.vertices[3 * i + 2];
    }
    
    /* calculate model width, height, and depth */
    w = glmAbs(maxx) + glmAbs(minx);
    h = glmAbs(maxy) + glmAbs(miny);
    d = glmAbs(maxz) + glmAbs(minz);
    
    /* calculate center of the model */
    cx = (maxx + minx) / 2.0f;
    cy = (maxy + miny) / 2.0f;
    cz = (maxz + minz) / 2.0f;
    
    /* calculate unitizing scale factor */
    scale = 2.0f / glmMax(glmMax(w, h), d);
    
    /* translate around center then scale */
    for (i = 1; i <= model.numvertices; i++) {
        model.vertices[3 * i + 0] -= cx;
        model.vertices[3 * i + 1] -= cy;
        model.vertices[3 * i + 2] -= cz;
        model.vertices[3 * i + 0] *= scale;
        model.vertices[3 * i + 1] *= scale;
        model.vertices[3 * i + 2] *= scale;
    }
    
    return scale;
}

/* glmDimensions: Calculates the dimensions (width, height, depth) of
 * a model.
 *
 * model   - initialized GLMmodel structure
 * dimensions - array of 3 floats (float dimensions[3])
 */
static float[]
glmDimensions(GLMmodel model)
{
    float[] dimensions = new float[3];  
    int i;
    float maxx, minx, maxy, miny, maxz, minz;
    
   // assert(model);
   // assert(model.vertices);
   // assert(dimensions);
    
    /* get the max/mins */
    maxx = minx = model.vertices[3 + 0];
    maxy = miny = model.vertices[3 + 1];
    maxz = minz = model.vertices[3 + 2];
    for (i = 1; i <= model.numvertices; i++) {
        if (maxx < model.vertices[3 * i + 0])
            maxx = model.vertices[3 * i + 0];
        if (minx > model.vertices[3 * i + 0])
            minx = model.vertices[3 * i + 0];
        
        if (maxy < model.vertices[3 * i + 1])
            maxy = model.vertices[3 * i + 1];
        if (miny > model.vertices[3 * i + 1])
            miny = model.vertices[3 * i + 1];
        
        if (maxz < model.vertices[3 * i + 2])
            maxz = model.vertices[3 * i + 2];
        if (minz > model.vertices[3 * i + 2])
            minz = model.vertices[3 * i + 2];
    }
    
    /* calculate model width, height, and depth */
    dimensions[0] = glmAbs(maxx) + glmAbs(minx);
    dimensions[1] = glmAbs(maxy) + glmAbs(miny);
    dimensions[2] = glmAbs(maxz) + glmAbs(minz);
	 return dimensions;
}

static float[]
glmDimensionsMinMax(GLMmodel model)
{
    float[] dimensions = new float[6];  
    int i;
    float maxx, minx, maxy, miny, maxz, minz;
    
   // assert(model);
   // assert(model.vertices);
   // assert(dimensions);
    
    /* get the max/mins */
    maxx = minx = model.vertices[3 + 0];
    maxy = miny = model.vertices[3 + 1];
    maxz = minz = model.vertices[3 + 2];
    for (i = 1; i <= model.numvertices; i++) {
        if (maxx < model.vertices[3 * i + 0])
            maxx = model.vertices[3 * i + 0];
        if (minx > model.vertices[3 * i + 0])
            minx = model.vertices[3 * i + 0];
        
        if (maxy < model.vertices[3 * i + 1])
            maxy = model.vertices[3 * i + 1];
        if (miny > model.vertices[3 * i + 1])
            miny = model.vertices[3 * i + 1];
        
        if (maxz < model.vertices[3 * i + 2])
            maxz = model.vertices[3 * i + 2];
        if (minz > model.vertices[3 * i + 2])
            minz = model.vertices[3 * i + 2];
    }
    
    /* calculate model width, height, and depth */
    dimensions[0] = minx;
    dimensions[1] = miny;
    dimensions[2] = minz;
    dimensions[3] = maxx;
    dimensions[4] = maxy;
    dimensions[5] = maxz;	 
	 return dimensions;
}





/* glmScale: Scales a model by a given amount.
 * 
 * model - properly initialized GLMmodel structure
 * scale - scalefactor (0.5 = half as large, 2.0 = twice as large)
 */
static void
glmScale(GLMmodel model, float scale)
{
    int i;
    
    for (i = 1; i <= model.numvertices; i++) {
        model.vertices[3 * i + 0] *= scale;
        model.vertices[3 * i + 1] *= scale;
        model.vertices[3 * i + 2] *= scale;
    }
}



static void
glmTranslate(GLMmodel model, float x, float y, float z)
{
    int i;
    
    for (i = 1; i <= model.numvertices; i++) {
        model.vertices[3 * i + 0] += x;
        model.vertices[3 * i + 1] += y;
        model.vertices[3 * i + 2] += z;
    }
}

/* glmReverseWinding: Reverse the polygon winding for all polygons in
 * this model.   Default winding is counter-clockwise.  Also changes
 * the direction of the normals.
 * 
 * model - properly initialized GLMmodel structure 
 */
void
glmReverseWinding(GLMmodel model)
{
    int i, swap;
    
   // assert(model);
  
       // model.triangles[i] =  model.triangle[i]
	 
	 
    for (i = 0; i < model.numtriangles; i++) {
        swap =model.triangles[i].vindices[0];
       model.triangles[i].vindices[0] =model.triangles[i].vindices[2];
       model.triangles[i].vindices[2] = swap;
        
        if (model.numnormals != 0) {
            swap =model.triangles[i].nindices[0];
           model.triangles[i].nindices[0] =model.triangles[i].nindices[2];
           model.triangles[i].nindices[2] = swap;
        }
        
        if (model.numtexcoords != 0) {
            swap =model.triangles[i].tindices[0];
           model.triangles[i].tindices[0] =model.triangles[i].tindices[2];
           model.triangles[i].tindices[2] = swap;
        }
    }
    
    /* reverse facet normals */
    for (i = 1; i <= model.numfacetnorms; i++) {
        model.facetnorms[3 * i + 0] = -model.facetnorms[3 * i + 0];
        model.facetnorms[3 * i + 1] = -model.facetnorms[3 * i + 1];
        model.facetnorms[3 * i + 2] = -model.facetnorms[3 * i + 2];
    }
    
    /* reverse vertex normals */
    for (i = 1; i <= model.numnormals; i++) {
        model.normals[3 * i + 0] = -model.normals[3 * i + 0];
        model.normals[3 * i + 1] = -model.normals[3 * i + 1];
        model.normals[3 * i + 2] = -model.normals[3 * i + 2];
    }
}

/* glmFacetNormals: Generates facet normals for a model (by taking the
 * cross product of the two vectors derived from the sides of each
 * triangle).  Assumes a counter-clockwise winding.
 *
 * model - initialized GLMmodel structure
 */
static void
glmFacetNormals(GLMmodel model)
{
    int  i;
    float[] u = new float[3];
    float[] v = new float[3];
    
   // assert(model);
   // assert(model.vertices);
    
    /* clobber any old facetnormals */
    if (model.facetnorms != null)
        model.facetnorms = null;
    
    /* allocate memory for the new facet normals */
    model.numfacetnorms = model.numtriangles;
    model.facetnorms = new float[3 * (model.numfacetnorms + 1)];
    
    for (i = 0; i < model.numtriangles; i++) {
        model.triangles[i].findex = i+1;
        
        u[0] = model.vertices[3 *model.triangles[i].vindices[1] + 0] -
            model.vertices[3 *model.triangles[i].vindices[0] + 0];
        u[1] = model.vertices[3 *model.triangles[i].vindices[1] + 1] -
            model.vertices[3 *model.triangles[i].vindices[0] + 1];
        u[2] = model.vertices[3 *model.triangles[i].vindices[1] + 2] -
            model.vertices[3 *model.triangles[i].vindices[0] + 2];
        
        v[0] = model.vertices[3 *model.triangles[i].vindices[2] + 0] -
            model.vertices[3 *model.triangles[i].vindices[0] + 0];
        v[1] = model.vertices[3 *model.triangles[i].vindices[2] + 1] -
            model.vertices[3 *model.triangles[i].vindices[0] + 1];
        v[2] = model.vertices[3 *model.triangles[i].vindices[2] + 2] -
            model.vertices[3 *model.triangles[i].vindices[0] + 2];
        
        glmCross(u, v, model.facetnorms,3 * (i+1) );
        glmNormalize(model.facetnorms,3 * (i+1));
    }
}

/* glmVertexNormals: Generates smooth vertex normals for a model.
 * First builds a list of all the triangles each vertex is in.   Then
 * loops through each vertex in the the list averaging all the facet
 * normals of the triangles each vertex is in.   Finally, sets the
 * normal index in the triangle for the vertex to the generated smooth
 * normal.   If the dot product of a facet normal and the facet normal
 * associated with the first triangle in the list of triangles the
 * current vertex is in is greater than the cosine of the angle
 * parameter to the function, that facet normal is not added into the
 * average normal calculation and the corresponding vertex is given
 * the facet normal.  This tends to preserve hard edges.  The angle to
 * use depends on the model, but 90 degrees is usually a good start.
 *
 * model - initialized GLMmodel structure
 * angle - maximum angle (in degrees) to smooth across
 */
static void
glmVertexNormals(GLMmodel model, float angle)
{
    GLMnode node;
    GLMnode tail;
    GLMnode[] members;
    float[] normals;
    int numnormals;
    float[] average = new float[3];
    float dot, cos_angle;
    int i;
	 int avg;
    
  //  assert(model);
  //  assert(model.facetnorms);
    
    /* calculate the cosine of the angle (in degrees) */
    cos_angle = (float) Math.cos(angle * Math.PI / 180.0);
    
    /* nuke any previous normals */
    if (model.normals != null)
        model.normals = null;
    
    /* allocate space for new normals */
    model.numnormals = model.numtriangles * 3; /* 3 normals per triangle */
    model.normals = new float[3* (model.numnormals+1)] ;
    
    /* allocate a structure that will hold a linked list of triangle
    indices for each vertex */
    members = new GLMnode[model.numvertices + 1];
    for (i = 1; i <= model.numvertices; i++)
        members[i] = null;
    
    /* for every triangle, create a node for each vertex in it */
    for (i = 0; i < model.numtriangles; i++) {
        node = new GLMnode();
        node.index = i;
        node.next  = members[model.triangles[i].vindices[0]];
        members[model.triangles[i].vindices[0]] = node;
        
        node = new GLMnode();
        node.index = i;
        node.next  = members[model.triangles[i].vindices[1]];
        members[model.triangles[i].vindices[1]] = node;
        
        node = new GLMnode();
        node.index = i;
        node.next  = members[model.triangles[i].vindices[2]];
        members[model.triangles[i].vindices[2]] = node;
    }
    
    /* calculate the average normal for each vertex */
    numnormals = 1;
    for (i = 1; i <= model.numvertices; i++) {
    /* calculate an average normal for this vertex by averaging the
        facet normal of every triangle this vertex is in */
        node = members[i];
        if (node == null) {
            System.out.print("glmVertexNormals(): vertex w/o a triangle\n");
				//System.exit(1);
				continue;   // if it's not in any triangle,
				            // then we won't be rendering with it?  jm
		  }
        average[0] = 0.0f; average[1] = 0.0f; average[2] = 0.0f;
        avg = 0;
        while (node != null) {
        /* only average if the dot product of the angle between the two
        facet normals is greater than the cosine of the threshold
        angle -- or, said another way, the angle between the two
            facet normals is less than (or equal to) the threshold angle */
            dot = glmDot(
				    model.facetnorms,3 * model.triangles[node.index].findex,
                model.facetnorms,3 * model.triangles[members[i].index].findex
					 );
            if (dot > cos_angle) {
                node.averaged = true;
                average[0] += model.facetnorms[3 * model.triangles[node.index].findex + 0];
                average[1] += model.facetnorms[3 * model.triangles[node.index].findex + 1];
                average[2] += model.facetnorms[3 * model.triangles[node.index].findex + 2];
                avg = 1;            /* we averaged at least one normal! */
            } else {
                node.averaged = false;
            }
            node = node.next;
        }
        
        if (avg != 0) {
            /* normalize the averaged normal */
            glmNormalize(average, 0);
            
            /* add the normal to the vertex normals list */
            model.normals[3 * numnormals + 0] = average[0];
            model.normals[3 * numnormals + 1] = average[1];
            model.normals[3 * numnormals + 2] = average[2];
            avg = numnormals;
            numnormals++;
        }
        
        /* set the normal of this vertex in each triangle it is in */
        node = members[i];
        while (node != null) {
            if (node.averaged) {
                /* if this node was averaged, use the average normal */
                if (model.triangles[node.index].vindices[0] == i)
                    model.triangles[node.index].nindices[0] = avg;
                else if (model.triangles[node.index].vindices[1] == i)
                    model.triangles[node.index].nindices[1] = avg;
                else if (model.triangles[node.index].vindices[2] == i)
                    model.triangles[node.index].nindices[2] = avg;
            } else {
                /* if this node wasn't averaged, use the facet normal */
                model.normals[3 * numnormals + 0] = 
                    model.facetnorms[3 * model.triangles[node.index].findex + 0];
                model.normals[3 * numnormals + 1] = 
                    model.facetnorms[3 * model.triangles[node.index].findex + 1];
                model.normals[3 * numnormals + 2] = 
                    model.facetnorms[3 * model.triangles[node.index].findex + 2];
                if (model.triangles[node.index].vindices[0] == i)
                    model.triangles[node.index].nindices[0] = numnormals;
                else if (model.triangles[node.index].vindices[1] == i)
                    model.triangles[node.index].nindices[1] = numnormals;
                else if (model.triangles[node.index].vindices[2] == i)
                    model.triangles[node.index].nindices[2] = numnormals;
                numnormals++;
            }
            node = node.next;
        }
    }
    
    model.numnormals = numnormals - 1;
    
    /* free the member information */
    for (i = 1; i <= model.numvertices; i++) {
        node = members[i];
        while (node != null) {
            tail = node;
            node = node.next;
            tail = null;
        }
    }
    members = null;
    
    /* pack the normals array (we previously allocated the maximum
    number of normals that could possibly be created (numtriangles *
    3), so get rid of some of them (usually alot unless none of the
    facet normals were averaged)) */
    normals = model.normals;
    model.normals = new float[3* (model.numnormals+1)];
    for (i = 1; i <= model.numnormals; i++) {
        model.normals[3 * i + 0] = normals[3 * i + 0];
        model.normals[3 * i + 1] = normals[3 * i + 1];
        model.normals[3 * i + 2] = normals[3 * i + 2];
    }
    normals = null;
}



/* glmWriteOBJ: Writes a model description in Wavefront .OBJ format to
 * a file.
 *
 * model - initialized GLMmodel structure
 * filename - name of the file to write the Wavefront .OBJ format data to
 * mode  - a bitwise or of values describing what is written to the file
 *             GLM_NONE     -  render with only vertices
 *             GLM_FLAT     -  render with facet normals
 *             GLM_SMOOTH   -  render with vertex normals
 *             GLM_TEXTURE  -  render with texture coords
 *             GLM_COLOR    -  render with colors (color material)
 *             GLM_MATERIAL -  render with materials
 *             GLM_COLOR and GLM_MATERIAL should not both be specified.  
 *             GLM_FLAT and GLM_SMOOTH should not both be specified.  
 */
static void
glmWriteOBJ(GLMmodel model, String filename, int mode)
{
    int i;
   // FILE* file;

    GLMgroup group;
    
   // assert(model);
    
    /* do a bit of warning */
    if ((mode & GLM_FLAT) != 0 && model.facetnorms == null) {
        System.out.print("glmWriteOBJ() warning: flat normal output requested "+
            "with no facet normals defined.\n");
        mode &= ~GLM_FLAT;
    }
    if ((mode & GLM_SMOOTH) != 0 && model.normals == null) {
        System.out.print("glmWriteOBJ() warning: smooth normal output requested "+
            "with no normals defined.\n");
        mode &= ~GLM_SMOOTH;
    }
    if ((mode & GLM_TEXTURE) != 0 && model.texcoords == null) {
        System.out.print("glmWriteOBJ() warning: texture coordinate output requested "+
            "with no texture coordinates defined.\n");
        mode &= ~GLM_TEXTURE;
    }
    if ((mode & GLM_FLAT) != 0 && (mode & GLM_SMOOTH) != 0) {
        System.out.print("glmWriteOBJ() warning: flat normal output requested "+
            "and smooth normal output requested (using smooth).\n");
        mode &= ~GLM_FLAT;
    }
    if ((mode & GLM_COLOR) != 0 && model.materials == null) {
        System.out.print("glmWriteOBJ() warning: color output requested "+
            "with no colors (materials) defined.\n");
        mode &= ~GLM_COLOR;
    }
    if ((mode & GLM_MATERIAL) != 0 && model.materials == null) {
        System.out.print("glmWriteOBJ() warning: material output requested "+
            "with no materials defined.\n");
        mode &= ~GLM_MATERIAL;
    }
    if ((mode & GLM_COLOR) != 0 && (mode & GLM_MATERIAL) != 0) {
        System.out.print("glmWriteOBJ() warning: color and material output requested "+
            "outputting only materials.\n");
        mode &= ~GLM_COLOR;
    }
    
    PrintStream str = null;
	 
	 try {
	    str = new PrintStream(new File(filename));
	 } catch(Exception e) {
	 }
    /* open the file */
	 /*
    file = fopen(filename, "w");
    if (!file) {
        fstr.print(stderr, "glmWriteOBJ() failed: can't open file \"%s\" to write.\n",
            filename);
        exit(1);
    }
	 */
    
    /* spit out a header */
    str.print( "#  \n");
    str.print( "#  Wavefront OBJ generated by GLM library\n");
    str.print( "#  \n");
    str.print( "#  GLM library\n");
    str.print( "#  Nate Robins\n");
    str.print( "#  ndr@pobox.com\n");
    str.print( "#  http://www.pobox.com/~ndr\n");
    str.print( "#  \n");
    
    if ((mode & GLM_MATERIAL) != 0 && model.mtllibname != null) {
        str.print( "\nmtllib "+ model.mtllibname+"\n\n");
   //     glmWriteMTL(model, filename, model.mtllibname);
    }
    
    /* spit out the vertices */
    str.print( "\n");
    str.print( "# "+ model.numvertices +" vertices\n");
    for (i = 1; i <= model.numvertices; i++) {
        str.print( "v "+ model.vertices[3 * i + 0]+" "
		                 + model.vertices[3 * i + 1]+" "
							  + model.vertices[3 * i + 2]+"\n");
    }
    
    /* spit out the smooth/flat normals */
    if ((mode & GLM_SMOOTH) != 0) {
        str.print( "\n");
        str.print( "# "+ model.numnormals +" normals\n");
        for (i = 1; i <= model.numnormals; i++) {
            str.print( "vn "+ model.normals[3 * i + 0]+" "
				         +model.normals[3 * i + 1]+" "
							+ model.normals[3 * i + 2]+"\n"
               );
        }
    } else if ((mode & GLM_FLAT) != 0) {
        str.print( "\n");
        str.print( "# "+  model.numfacetnorms+" normals\n");
        for (i = 1; i <= model.numnormals; i++) {
            str.print( "vn "+model.facetnorms[3 * i + 0] +" "
				   + model.facetnorms[3 * i + 1] +" "
					+ model.facetnorms[3 * i + 2] +"\n"
               );
        }
    }
    
    /* spit out the texture coordinates */
    if ((mode & GLM_TEXTURE) != 0) {
        str.print( "\n");
        str.print( "# "+ model.numtexcoords +" texcoords\n");
        for (i = 1; i <= model.numtexcoords; i++) {
            str.print( "vt "+  model.texcoords[2 * i + 0]+
				" "+ model.texcoords[2 * i + 1] +"\n"
               );
        }
    }
    
    str.print( "\n");
    str.print( "# "+ model.numgroups +" groups\n");
    str.print( "# "+  model.numtriangles +" faces (triangles)\n");
    str.print( "\n");
    
    group = model.groups;
    while(group != null) {
        str.print( "g "+ group.name+"\n");
        if ((mode & GLM_MATERIAL) != 0)
            str.print( "usemtl "+model.materials[group.material].name+"\n" );
        for (i = 0; i < group.numtriangles; i++) {
            if ((mode & GLM_SMOOTH) != 0 && (mode & GLM_TEXTURE) != 0) {
                str.printf(
					 "f %d/%d/%d %d/%d/%d %d/%d/%d\n",
					    // "f "+ +"/"+ +"/"+ +" "+ +"/"+ +"/"+ +" "+ +"/"+ +"/"+ +"\n",
                    model.triangles[group.triangles[i]].vindices[0], 
                    model.triangles[group.triangles[i]].tindices[0],
                    model.triangles[group.triangles[i]].nindices[0], 
                    model.triangles[group.triangles[i]].vindices[1],
                    model.triangles[group.triangles[i]].tindices[1],
                    model.triangles[group.triangles[i]].nindices[1],
                    model.triangles[group.triangles[i]].vindices[2],
                    model.triangles[group.triangles[i]].tindices[2],
                    model.triangles[group.triangles[i]].nindices[2]);
            } else if ((mode & GLM_FLAT) != 0 && (mode & GLM_TEXTURE) != 0) {
                str.printf("f %d/%d %d/%d %d/%d\n",
                    model.triangles[group.triangles[i]].vindices[0],
                    model.triangles[group.triangles[i]].findex,
                    model.triangles[group.triangles[i]].vindices[1],
                    model.triangles[group.triangles[i]].findex,
                    model.triangles[group.triangles[i]].vindices[2],
                    model.triangles[group.triangles[i]].findex);
            } else if ((mode & GLM_TEXTURE) != 0) {
                str.printf("f %d/%d %d/%d %d/%d\n",
                    model.triangles[group.triangles[i]].vindices[0],
                    model.triangles[group.triangles[i]].tindices[0],
                    model.triangles[group.triangles[i]].vindices[1],
                    model.triangles[group.triangles[i]].tindices[1],
                    model.triangles[group.triangles[i]].vindices[2],
                    model.triangles[group.triangles[i]].tindices[2]);
            } else if ((mode & GLM_SMOOTH)!= 0) {
                str.printf("f %d//%d %d//%d %d//%d\n",
                    model.triangles[group.triangles[i]].vindices[0],
                    model.triangles[group.triangles[i]].nindices[0],
                    model.triangles[group.triangles[i]].vindices[1],
                    model.triangles[group.triangles[i]].nindices[1],
                    model.triangles[group.triangles[i]].vindices[2], 
                    model.triangles[group.triangles[i]].nindices[2]);
            } else if ((mode & GLM_FLAT) != 0) {
                str.printf("f %d//%d %d//%d %d//%d\n",
                    model.triangles[group.triangles[i]].vindices[0], 
                    model.triangles[group.triangles[i]].findex,
                    model.triangles[group.triangles[i]].vindices[1],
                    model.triangles[group.triangles[i]].findex,
                    model.triangles[group.triangles[i]].vindices[2],
                    model.triangles[group.triangles[i]].findex);
            } else {
                str.printf("f %d %d %d\n",
                    model.triangles[group.triangles[i]].vindices[0],
                    model.triangles[group.triangles[i]].vindices[1],
                    model.triangles[group.triangles[i]].vindices[2]);
            }
        }
        str.print( "\n");
        group = group.next;
    }
    
   // fclose(file);
}


static void proto(GLMmodel model) {
    GLMgroup group = model.groups;
	 int groupCount = 0;
	 boolean skip = ! false;
    while (group != null) {
	     System.out.println(group.name);
	     groupCount++;
		  if (groupCount > 3) 
		      break;
        for (int i = 0; i < group.numtriangles; i++) {
            GLMtriangle triangle = model.triangles[group.triangles[i]];
			//	System.out.println(triangle.vindices[0]+" "
			//	                   +triangle.vindices[1]+" "
			//							 +triangle.vindices[2]);
		  }		
				
		  group = group.next;
	 }
}

/* glmDraw: Renders the model to the current OpenGL context using the
 * mode specified.
 *
 * model - initialized GLMmodel structure
 * mode  - a bitwise OR of values describing what is to be rendered.
 *             GLM_NONE     -  render with only vertices
 *             GLM_FLAT     -  render with facet normals
 *             GLM_SMOOTH   -  render with vertex normals
 *             GLM_TEXTURE  -  render with texture coords
 *             GLM_COLOR    -  render with colors (color material)
 *             GLM_MATERIAL -  render with materials
 *             GLM_COLOR and GLM_MATERIAL should not both be specified.  
 *             GLM_FLAT and GLM_SMOOTH should not both be specified.  
 */
static void
glmDraw(GLMmodel model, int mode, GL2 gl)
{
   
    int i;
    GLMgroup group;
    GLMtriangle triangle;
    GLMmaterial material = null;
    
    //assert(model);
    //assert(model.vertices);
    
    /* do a bit of warning */
    if ((mode & GLM_FLAT) != 0 && model.facetnorms == null) {
        System.out.print("glmDraw() warning: flat render mode requested "+
            "with no facet normals defined.\n");
        mode &= ~GLM_FLAT;
    }
    if ((mode & GLM_SMOOTH) != 0 && model.normals == null) {
        System.out.print("glmDraw() warning: smooth render mode requested "+
            "with no normals defined.\n");
        mode &= ~GLM_SMOOTH;
    }
    if ((mode & GLM_TEXTURE)  != 0 && model.texcoords == null) {
        System.out.print("glmDraw() warning: texture render mode requested "+
            "with no texture coordinates defined.\n");
        mode &= ~GLM_TEXTURE;
    }
    if ((mode & GLM_FLAT) != 0 && (mode & GLM_SMOOTH) != 0) {
        System.out.print("glmDraw() warning: flat render mode requested "+
            "and smooth render mode requested (using smooth).\n");
        mode &= ~GLM_FLAT;
    }
    if ((mode & GLM_COLOR) != 0 && model.materials == null) {
        System.out.print("glmDraw() warning: color render mode requested "+
            "with no materials defined.\n");
        mode &= ~GLM_COLOR;
    }
    if ((mode & GLM_MATERIAL)  != 0 && model.materials == null) {
        System.out.print("glmDraw() warning: material render mode requested "+
            "with no materials defined.\n");
        mode &= ~GLM_MATERIAL;
    }
    if ((mode & GLM_COLOR) != 0  && (mode & GLM_MATERIAL) != 0 ) {
        System.out.print("glmDraw() warning: color and material render mode requested "+
            "using only material mode.\n");
        mode &= ~GLM_COLOR;
    }
    if ((mode & GLM_COLOR) != 0 )
        gl.glEnable(GL2.GL_COLOR_MATERIAL);
    else if ((mode & GLM_MATERIAL) != 0 )
        gl.glDisable(GL2.GL_COLOR_MATERIAL);
    
    /* perhaps this loop should be unrolled into material, color, flat,
       smooth, etc. loops?  since most cpu's have good branch prediction
       schemes (and these branches will always go one way), probably
       wouldn't gain too much?  */
    
    group = model.groups;
	 boolean skip = ! false;
    while (group != null) {
	     //System.out.println("group = "+group.name);
		  gl.glPushMatrix();
		  for (int t = 0; t < group.transforms.size(); t++) {
		     GLMtransform tr = group.transforms.get(t);
		     switch(tr.type) {
			      case GLMtransform.ROT:
					   gl.glRotatef(tr.angle,tr.x,tr.y,tr.z);
					break;
			      case GLMtransform.SCA:
					   gl.glScalef(tr.x,tr.y,tr.z);
					break;
			      case GLMtransform.TRA:
					   gl.glTranslatef(tr.x,tr.y,tr.z);
					break;										
					
			  }
		  }


		  
        if ((mode & GLM_MATERIAL) != 0) {
            material = model.materials[group.material];
            gl.glMaterialfv(gl.GL_FRONT_AND_BACK, gl.GL_AMBIENT, 
				   FloatBuffer.wrap(material.ambient));
            gl.glMaterialfv(gl.GL_FRONT_AND_BACK, gl.GL_DIFFUSE, 
				    FloatBuffer.wrap(material.diffuse));
            gl.glMaterialfv(gl.GL_FRONT_AND_BACK, gl.GL_SPECULAR,
				   FloatBuffer.wrap( material.specular));
            gl.glMaterialf(gl.GL_FRONT_AND_BACK, gl.GL_SHININESS, material.shininess);
        }
        
        if ((mode & GLM_COLOR) != 0) {
            gl.glColor3fv(FloatBuffer.wrap(material.diffuse));
        }
        
	//////////////////////////////////////////		
	if (group.displayListNum == -1) {
	     group.displayListNum = GLMgroup.displayListCounter++;
	     gl.glNewList(group.displayListNum, gl.GL_COMPILE);  
		  
        gl.glBegin(model.wireFrame ?  gl.GL_LINE_STRIP : gl.GL_TRIANGLES);
        for (i = 0; i < group.numtriangles; i++) {
            triangle = model.triangles[group.triangles[i]];
            
				int[] arr = {1,2,3}; 
            if ((mode & GLM_FLAT) != 0)
                gl.glNormal3f(
					    // FloatBuffer.wrap(
						     //new float[3] { 
							  model.facetnorms[3 * triangle.findex],
							  model.facetnorms[3 * triangle.findex+1],
							  model.facetnorms[3 * triangle.findex+2]
											// }
										//	 )
					               );
            
            if ((mode & GLM_SMOOTH) != 0)
                gl.glNormal3f(model.normals[3 * triangle.nindices[0]],
					             model.normals[3 * triangle.nindices[0]+1],
									 model.normals[3 * triangle.nindices[0]+2]
					             );
            if ((mode & GLM_TEXTURE) != 0)
                gl.glTexCoord2f( model.texcoords[2 * triangle.tindices[0]],
					                model.texcoords[2 * triangle.tindices[0]+1]
					               );
            gl.glVertex3f(  model.vertices[3 * triangle.vindices[0]]  ,
				              model.vertices[3 * triangle.vindices[0]+1]  ,
								  model.vertices[3 * triangle.vindices[0]+2]  
				            );
           // System.out.println(  model.vertices[3 * triangle.vindices[0]] +" "+
			//	              model.vertices[3 * triangle.vindices[0]+1]  +" "+
			//					  model.vertices[3 * triangle.vindices[0]+2]  
			//	            );								
            
            if ((mode & GLM_SMOOTH) != 0)
                gl.glNormal3f(model.normals[3 * triangle.nindices[1]],
					                model.normals[3 * triangle.nindices[1]+1],
					                model.normals[3 * triangle.nindices[1]+2]
					             );
            if ((mode & GLM_TEXTURE) != 0)
                gl.glTexCoord2f(model.texcoords[2 * triangle.tindices[1]],
					               model.texcoords[2 * triangle.tindices[1]+1] 
					      );
            
				gl.glVertex3f(model.vertices[3 * triangle.vindices[1]],
				            model.vertices[3 * triangle.vindices[1]+1],
								model.vertices[3 * triangle.vindices[1]+2]
				       );
           // System.out.println(  model.vertices[3 * triangle.vindices[1]] +" "+
			//	              model.vertices[3 * triangle.vindices[1]+1]  +" "+
			//					  model.vertices[3 * triangle.vindices[1]+2]  
			//	            );							 
            
            if ((mode & GLM_SMOOTH) != 0)
                gl.glNormal3f(model.normals[3 * triangle.nindices[2]],
					               model.normals[3 * triangle.nindices[2]+1],
										model.normals[3 * triangle.nindices[2]+2]
					     );
  
            if ((mode & GLM_TEXTURE) != 0)
                gl.glTexCoord2f(model.texcoords[2 * triangle.tindices[2]],
					                 model.texcoords[2 * triangle.tindices[2]+1]
					     );
    
	         gl.glVertex3f(model.vertices[3 * triangle.vindices[2]],
				               model.vertices[3 * triangle.vindices[2]+1],
									model.vertices[3 * triangle.vindices[2]+2]
				               );
        //    System.out.println(  model.vertices[3 * triangle.vindices[2]] +" "+
			//	              model.vertices[3 * triangle.vindices[2]+1]  +" "+
			//					  model.vertices[3 * triangle.vindices[2]+2]  
			//	            );										
            
        }
        gl.glEnd();

	
	
	    //////////////////////////////////////////		  
	    gl.glEndList();	 
	} // end of display list creation	
	
	gl.glCallList(group.displayListNum);
		  
		  gl.glPopMatrix();
        
        group = group.next;
    }
}





}